
/*
  Author:
  Lu Yiming <lu.yiming.lu@gmail.com>
 */

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <time.h>
#include <math.h>

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "usage.h"
#include "shm_sysv.h"
#include "data.h"


#define SHM_SYSV_SHM_FTOK_FILE "/tmp/_conn_shm_sysv_shm"
#define SHM_SYSV_SEM_FTOK_FILE "/tmp/_conn_shm_sysv_sem"
#define SHM_SYSV_MSG_FTOK_FILE "/tmp/_conn_shm_sysv_msg"

void shm_create_keyfile(const char *fname)
{
	int fd;
	struct stat s;

	if (stat(fname, &s)) {
		if (ENOENT == errno) {
			fd = open(fname, O_CREAT | O_WRONLY | O_TRUNC, 00600);
			if (-1 == fd) {
				fprintf(stderr, "error on creat file: %s", fname);
				exit(EXIT_FAILURE);
			}
			close(fd);
		} else {
			fprintf(stderr, "error on stat: %s", fname);
			exit(EXIT_FAILURE);
		}
	}
}

int shm_sysv_init(void **shm_addr, int *semid, int *msgid)
{
	key_t shm_key;
	key_t sem_key;
	key_t msg_key;

	/* check shm file for ftok(), if do not exist, create it */
	shm_create_keyfile(SHM_SYSV_SHM_FTOK_FILE);
	shm_create_keyfile(SHM_SYSV_SEM_FTOK_FILE);
	shm_create_keyfile(SHM_SYSV_MSG_FTOK_FILE);

	shm_key = ftok(SHM_SYSV_SHM_FTOK_FILE, 1);
	sem_key = ftok(SHM_SYSV_SEM_FTOK_FILE, 1);
	msg_key = ftok(SHM_SYSV_MSG_FTOK_FILE, 1);

	/* 4KB memory */
	int shmid = shmget(shm_key, 4096, IPC_CREAT | SHM_NORESERVE | 0600);
	if (-1 == shmid) {
		fprintf(stderr, "shmget");
		exit(EXIT_FAILURE);
	}
	*shm_addr = shmat(shmid, NULL, 0);
	if ((void*)-1 == *shm_addr) {
		fprintf(stderr, "shmat");
		exit(EXIT_FAILURE);
	}

	*semid = semget(sem_key, 1, IPC_CREAT | 0600);
	if (-1 == *semid) {
		fprintf(stderr, "semget");
		exit(EXIT_FAILURE);
	}

	*msgid = msgget(msg_key, IPC_CREAT | 0600);
	if (-1 == *msgid) {
		fprintf(stderr, "msgget");
		exit(EXIT_FAILURE);
	}

	return 0;
}

void shm_sysv_server(int argc, char **argv)
{
	unsigned int i;
	void *buf_recv;
	int old_argc = argc;
	int semid;
	int msgid;
	int recv;
	int sent;
        struct { long type; char text[1]; } msg = {1, ""};
	/* struct sembuf sem[2]; */
	int debug = 0;

	while (argc > 0) {
		/* for future  
		if ((argc > 1) &&
		    (0 == strcmp(argv[0], "port"))) {
			port = atoi(argv[1]);
			if (!port) {
				fprintf(stderr, "port can't be 0\n");
				exit(EXIT_FAILURE);
			}
			argc -= 2;
			argv += 2;
		}
		*/

		if ((argc > 0) &&
		    (0 == strcmp(argv[0], "debug"))) {
			debug = 1;
			argc -= 1;
			argv += 1;
		}

		if (old_argc == argc) {
			usage();
			exit (EXIT_FAILURE);
		}
		old_argc = argc;
	}

	shm_sysv_init(&buf_recv, &semid, &msgid);
     
	printf("share memory (SYS V) server started\n");

	while(1) {
		recv = msgrcv(msgid, &msg, sizeof(msg), 0, 0);
		if (-1 == recv) {
			perror("msgrcv");
			exit(EXIT_FAILURE);
		}

		if (debug) {
			fprintf (stderr, "Server: got data, ID: %ld\n", *((unsigned long int *)buf_recv));
		}

		/* sem[0].sem_num = 0; */
		/* sem[1].sem_num = 0; */
		/* sem[0].sem_flg = SEM_UNDO; /\* Release semaphore on exit *\/ */
		/* sem[1].sem_flg = SEM_UNDO; /\* Release semaphore on exit *\/ */

		/* /\* Set up two semaphore operations *\/ */
		/* sem[0].sem_op = 0; /\* Wait for zero *\/ */
		/* sem[1].sem_op = 1; /\* Add 1 to lock it*\/ */
		/* semop(semid, sem, 2); */
		/* enter critical area... */

		/* leave critical area... */
		/* sem[0].sem_op = -1; /\* Decrement to unlock *\/ */
		/* semop(semid, sem, 1); */

		if (*(((unsigned int *)buf_recv + 2)) & (1 << 31)) {
			/* bit 31 means full-deplex */
			for (i = 0; i < 1024 - 8 - 4; i++) {  /* FIXME: loop count should be get from buf_recv */
				((char*)buf_recv)[8 + 4 + i] = 0xA5;
			}
		}

		sent = msgsnd(msgid, &msg, sizeof(msg), 0);
		if (-1 == sent) {
			perror("msgsnd");
			exit(EXIT_FAILURE);
		}
	}
}

void shm_sysv_client (int argc, char **argv)
{
	unsigned long int i;
	void *buf_send;
	time_t time_start;
	time_t time_end;
	unsigned long int packets = 128 * 1024;
	char duplex = 1;
	int old_argc = argc;
	int semid;
	int msgid;
	int recv;
	int sent;
	unsigned int j;
        struct { long type; char text[1]; } msg = {1, ""};

	while (argc > 0) {
		if ((argc > 1) &&
		    (0 == strcmp(argv[0], "packets"))) {
			packets = atoll(argv[1]);
			argc -= 2;
			argv += 2;
		}
		if ((argc > 0) &&
		    (0 == strcmp(argv[0], "full-duplex"))) {
			duplex = 2;
			argc -= 1;
			argv += 1;
		}
		if ((argc > 0) &&
		    (0 == strcmp(argv[0], "half-duplex"))) {
			duplex = 1;
			argc -= 1;
			argv += 1;
		}
		if (old_argc == argc) {
			usage();
			exit (EXIT_FAILURE);
		}
		old_argc = argc;
	}

	shm_sysv_init(&buf_send, &semid, &msgid);

	time(&time_start);

	for (i = 0; i < packets; i++) {

		/* buf[0-7] = ID, 0...n */
		*((unsigned long int *)buf_send) = i;
		/* buf[8-11] = message size */
		*(((unsigned int *)buf_send + 2)) = (1024 - 8 - 4) | 
		                                    ((1 == duplex) ? 0 : 1 << 31); /* bit 31 means full-deplex */

		sent = msgsnd(msgid, &msg, sizeof(msg), 0);
		if (-1 == sent) {
			perror("msgsnd");
			exit(EXIT_FAILURE);
		}

		if (2 == duplex) {
			for (j = 0; j < 1024 - 8 - 4; j++) {
				((char*)buf_send)[8 + 4 + j] = 0x5A;
			}

			if (*((unsigned long int *)buf_send) != i) {
				perror ("recv ID");
			}
			if ((*(((unsigned int *)buf_send) + 2) & 0xffffff) != (1024 - 8 - 4)) {
				perror ("recv message size");
			}
		}

		recv = msgrcv(msgid, &msg, sizeof(msg), 0, 0);
		if (-1 == recv) {
			perror("msgrcv");
			exit(EXIT_FAILURE);
		}
	}
     
	time(&time_end);

	fprintf (stderr, 
		 "Client: done. \n"
		 "Time elapsed (seconds): %'.2f\n"
		 "Packet (1KB) sent%s: %ld\n"
		 "Speed: %'.2f MBps\n"
		 , difftime(time_end, time_start)
		 , (1 == duplex) ? " (half-duplex)" : " and received (full-duplex)"
		 , packets
		 , 1.0 * packets * duplex / 1024 / ((0.0 == difftime(time_end, time_start)) ? 1.0 : difftime(time_end, time_start)));
     
}
